package AsLayers.Layer
{
	import AsLayers.BaseType.Bounds;
	import AsLayers.BaseType.Pixel;
	import AsLayers.BaseType.Size;
	import AsLayers.Map;
	import AsLayers.Tile.ITile;
	public class Grid implements ILayer
	{
		public  var tileSize:Size=new Size(255,255);
		public var grid:Array=new Array()
		public var map:Map
        public var buffer:int=1
        public var origin:Pixel
		public function Grid():void{
		}
		public function getMap():Map{
			return this.map
		}
		public function getTileSize():Size{
			return this.tileSize
		}
		
		public function setMap(map:Map):void{
			this.map=map;
			this.initGriddedTiles(this.map.getExtent());
		}
		//must be override in child class
		public function getUrl(bounds:Bounds):String{return null}
		public function initGriddedTiles(bounds:Bounds):void {
         var viewSize:Size = this.map.getSize();
         var minRows :int= Math.ceil(viewSize.h/this.tileSize.h) + 
                      Math.max(1, 2 * this.buffer);
         var minCols :int= Math.ceil(viewSize.w/this.tileSize.w) +
                      Math.max(1, 2 * this.buffer);
        
         var extent :Bounds= this.map.getMaxExtent();
         var resolution :Number= this.map.getResolution();
         var tilelon :Number= resolution * this.tileSize.w;
         var tilelat :Number= resolution * this.tileSize.h;
         var offsetlon :Number= bounds.left - extent.left;
         var tilecol:Number = Math.floor(offsetlon/tilelon) - this.buffer;
         var tilecolremain :Number= offsetlon/tilelon - tilecol;
         var tileoffsetx:Number = -tilecolremain * this.tileSize.w;
         var tileoffsetlon:Number = extent.left + tilecol * tilelon;
        
         var offsetlat:Number = bounds.top - (extent.bottom + tilelat);  
         var tilerow :Number= Math.ceil(offsetlat/tilelat) + this.buffer;
         var tilerowremain:Number = tilerow - offsetlat/tilelat;
         var tileoffsety :Number= -tilerowremain * this.tileSize.h;
         var tileoffsetlat :Number= extent.bottom + tilerow * tilelat;
        
         tileoffsetx = Math.round(tileoffsetx); // heaven help us
         tileoffsety = Math.round(tileoffsety);

         this.origin = new Pixel(tileoffsetx, tileoffsety);

         var startX:Number = tileoffsetx; 
         var startLon :Number= tileoffsetlon;

         var rowidx :int= 0;
    
         do {
            var row:Array = this.grid[rowidx++];
            if (!row) {
                row = [];
                this.grid.push(row);
            }

            tileoffsetlon = startLon;
            tileoffsetx = startX;
            var colidx :int= 0;
 
            do {
                var tileBounds:Bounds =  
                    new Bounds(tileoffsetlon, 
                                          tileoffsetlat, 
                                          tileoffsetlon + tilelon,
                                          tileoffsetlat + tilelat);

                var x:Number = tileoffsetx;
                x -= this.map.getLayerContainer().x;

                var y:Number = tileoffsety;
                y -= this.map.getLayerContainer().y;

                var px:Pixel = new Pixel(x, y);
                var tile:ITile = row[colidx++];
                if (!tile) {
                    tile = this.addTile(tileBounds, px);
 //                   this.addTileMonitoringHooks(tile);
                    row.push(tile);
                } else {
                    tile.moveTo(tileBounds, px, false);
                }
     
                tileoffsetlon += tilelon;       
                tileoffsetx += this.tileSize.w;
            } while ((tileoffsetlon <= bounds.right + tilelon * this.buffer)
                     || colidx < minCols)  
             
            tileoffsetlat -= tilelat;
            tileoffsety += this.tileSize.h;
        } while((tileoffsetlat >= bounds.bottom - tilelat * this.buffer)
                || rowidx < minRows)
             this.removeExcessTiles(rowidx, colidx);
         
        //now actually draw the tiles
        this.spiralTileLoad();
    }
    
        public function addTile(bounds:Bounds,position:Pixel):ITile{
        	return  null
		//	return new AsOpenLayers.Tile.Image(this, position, bounds, this.tileSize);
	    }
    
        public  function spiralTileLoad () :void{
           var tileQueue:Array = [];
           var directions:Array = ["right", "down", "left", "up"];

           var iRow:int = 0;
           var iCell:int = -1;
           var direction:int = 0;
           var directionsTried:int = 0;
        
           while( directionsTried < directions.length) {

              var testRow :int= iRow;
              var testCell :int= iCell;

              switch (directions[direction]) {
                case "right":
                    testCell++;
                    break;
                case "down":
                    testRow++;
                    break;
                case "left":
                    testCell--;
                    break;
                case "up":
                    testRow--;
                    break;
              } 
    
            // if the test grid coordinates are within the bounds of the 
            //  grid, get a reference to the tile.
              var tile:ITile = null;
              if ((testRow < this.grid.length) && (testRow >= 0) &&
                   (testCell < this.grid[0].length) && (testCell >= 0)) {
                    tile = this.grid[testRow][testCell];
               }
            
               if ((tile != null) && (!tile.isQueued())) {
                //add tile to beginning of queue, mark it as queued.
                   tileQueue.unshift(tile);
                   tile.setQueued(true) ;
                
                //restart the directions counter and take on the new coords
                   directionsTried = 0;
                   iRow = testRow;
                   iCell = testCell;
               } else {
                //need to try to load a tile in a different direction
                  direction = (direction + 1) % 4;
                   directionsTried++;
               }
           } 
        
        // now we go through and draw the tiles in forward order
           for(var i:int=0; i < tileQueue.length; i++) {
              tile = tileQueue[i]
                
              tile.draw();
            //mark tile as unqueued for the next time (since tiles are reused)
              tile.setQueued(false);       
           }
    }
        /**
     * Method: moveGriddedTiles
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     */
   public  function moveGriddedTiles(bounds:Bounds):void {
        var buffer:int = this.buffer || 1;
        while (true) {
            var tlLayer:Pixel = this.grid[0][0].position;
            var tlViewPort:Pixel =  this.map.getViewPortPxFromLayerPx(tlLayer);
            trace("tlViewPort:"+tlViewPort)
            trace(-this.tileSize.w * (buffer - 1))
            trace(-this.tileSize.w * buffer)
            if (tlViewPort.x > -this.tileSize.w * (buffer - 1)) {
                this.shiftColumn(true);
            } else if (tlViewPort.x < -this.tileSize.w * buffer) {
                this.shiftColumn(false);
            } else if (tlViewPort.y > -this.tileSize.h * (buffer - 1)) {
                this.shiftRow(true);
            } else if (tlViewPort.y < -this.tileSize.h * buffer) {
                this.shiftRow(false);
            } else {
                break;
            }
        };
        if (this.buffer == 0) {
            for (var r:int=0, rl:int=this.grid.length; r<rl; r++) {
                var row:Array = this.grid[r];
                for (var c:int=0, cl:int=row.length; c<cl; c++) {
                    var tile :ITile= row[c];
                    if (!tile.isDrawed() && 
                         tile.getBounds().intersectsBounds(bounds, false)) {
                         tile.draw();
                    }
                }
            }
        }
    }

    /**
     * Method: shiftRow
     * Shifty grid work
     *
     * Parameters:
     * prepend - {Boolean} if true, prepend to beginning.
     *                          if false, then append to end
     */
      public function moveTo(bounds:Bounds,zoomChanged:Boolean):void{
      	 // trace(this.getTilesBounds())
      	//  trace(bounds)
      	  trace(this.getTilesBounds().containsBounds(bounds,true))
      	  if(this.getTilesBounds().containsBounds(bounds,true)&&!zoomChanged){
      	  	 this.moveGriddedTiles(bounds)
      	  }else{
      	  	 this.initGriddedTiles(bounds);
      	  }
 	        
      }
      public function getTilesBounds():Bounds{
      	  var bounds:Bounds = null; 
          if (this.grid.length) {
            var bottom:int = this.grid.length - 1;
            var bottomLeftTile:ITile= this.grid[bottom][0];
    
            var right:int = this.grid[0].length - 1; 
            var topRightTile:ITile = this.grid[0][right];
    
            bounds = new Bounds(bottomLeftTile.getBounds().left, 
                                           bottomLeftTile.getBounds().bottom,
                                           topRightTile.getBounds().right, 
                                           topRightTile.getBounds().top);
            
          }   
          return bounds;
      }
    protected function shiftRow(prepend:Boolean):void {
        var modelRowIndex:int = (prepend) ? 0 : (this.grid.length - 1);
        var modelRow:Array = this.grid[modelRowIndex];

        var resolution:Number= this.map.getResolution();
        var deltaY:Number = (prepend) ? -this.tileSize.h : this.tileSize.h;
        var deltaLat:Number = resolution * -deltaY;

        var row:Array = (prepend) ? this.grid.pop() : this.grid.shift();

        for (var i:int=0; i < modelRow.length; i++) {
            var modelTile:ITile = modelRow[i];
            var bounds:Bounds = modelTile.getBounds().clone();
            var position:Pixel = modelTile.getPosition().clone();
            bounds.bottom = bounds.bottom + deltaLat;
            bounds.top = bounds.top + deltaLat;
            position.y = position.y + deltaY;
            row[i].moveTo(bounds, position,true);
        }

        if (prepend) {
            this.grid.unshift(row);
        } else {
            this.grid.push(row);
        }
    }

    /**
     * Method: shiftColumn
     * Shift grid work in the other dimension
     *
     * Parameters:
     * prepend - {Boolean} if true, prepend to beginning.
     *                          if false, then append to end
     */
    protected function shiftColumn(prepend:Boolean):void {
        var deltaX:Number = (prepend) ? -this.tileSize.w : this.tileSize.w;
        var resolution:Number = this.map.getResolution();
        var deltaLon:Number = resolution * deltaX;

        for (var i:int=0; i<this.grid.length; i++) {
            var row:Array = this.grid[i];
            var modelTileIndex:int = (prepend) ? 0 : (row.length - 1);
            var modelTile:ITile = row[modelTileIndex];
            
            var bounds:Bounds = modelTile.getBounds().clone();
            var position:Pixel = modelTile.getPosition().clone();
            bounds.left = bounds.left + deltaLon;
            bounds.right = bounds.right + deltaLon;
            position.x = position.x + deltaX;

            var tile:ITile = prepend ? this.grid[i].pop() : this.grid[i].shift()
            trace("new ps:"+position)
            tile.moveTo(bounds, position,true);
            if (prepend) {
                this.grid[i].unshift(tile);
            } else {
                this.grid[i].push(tile);
            }
        }
    }
     public function  removeExcessTiles(rows:int, columns:int):void {
        
        // remove extra rows
        var row:Array,i:int,l:int,tile:ITile;
        while (this.grid.length > rows) {
            row = this.grid.pop();
            for (i=0, l=row.length; i<l; i++) {
                tile = row[i];
               // this.removeTileMonitoringHooks(tile)
                tile.destroy();
                tile=null;
            }
        }
        
        // remove extra columns
        while (this.grid[0].length > columns) {
            for (i=0, l=this.grid.length; i<l; i++) {
                 row = this.grid[i];
                tile = row.pop();
            //    this.removeTileMonitoringHooks(tile);
                tile.destroy();
                tile=null;
            }
        }
    }
		
	}
}